package model;

import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Point;
import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
import java.util.Observable;
import util.coordTranslate.AbstractCoordinateSystem;
import util.resources.ResourceManager;
import view.Pen;

/**
 * The turtle.
 * 
 * @author Alex Lee and Nathan Klug
 */
public class Turtle extends Observable
{
    private static ResourceManager defaults = ResourceManager.getInstance();

    private static int nextId;

    /*
     * The basic data that represents a turtle.
     */
    private int myId;
    private double myX, myY;
    private Angle myAngle;
    private Pen myPen;
    private Image myImage;
    private boolean isVisible;
    private String myName;

    /*
     * Default values
     */
    public static double defaultX = defaults.getInteger("Turtle_x");
    public static double defaultY = defaults.getInteger("Turtle_y");

    private static Angle getDefaultAngle()
    {
        return new Angle();
    }

    private static boolean defaultVisibility = defaults
            .getBoolean("Turtle_visibility");

    private static Pen getDefaultPen()
    {
        return new Pen();
    }

    private static Image getDefaultImage()
    {
        return defaults.getImage("Turtle_icon_1");
    }

    private static String defaultName = defaults.getString("Turtle_name");

    /**
     * Creates a turtle with default values
     */
    public Turtle()
    {
        this(defaultX, defaultY, getDefaultAngle());
    }

    /**
     * Creates a turtle with initial position (x,y)
     * 
     * @param x
     *            the x coordinate
     * @param y
     *            the y coordinate
     */
    public Turtle(double x, double y)
    {
        this(x, y, getDefaultAngle());
    }

    /**
     * Creates a turtle with initial position (x,y) facing radians degrees from
     * the x axis. Note that an angle of 0 is facing right.
     * 
     * @param x
     *            the x coordinate
     * @param y
     *            the y coordinate
     * @param radians
     *            the angle the turtle is facing in radians
     */
    public Turtle(double x, double y, double radians)
    {
        this(x, y, new Angle(radians));
    }

    /**
     * Creates a turtle with initial position (x,y) facing the given Angle
     * 
     * @param x
     *            the x coordinate
     * @param y
     *            the y coordinate
     * @param angle
     *            the angle the turtle is facing
     */

    public Turtle(double x, double y, Angle angle)
    {
        this(x, y, angle, getDefaultPen());
    }

    /**
     * Creates a turtle with initial position (x,y) facing the given Angle
     * holding the given pen
     * 
     * @param x
     *            the x coordinate
     * @param y
     *            the y coordinate
     * @param angle
     *            the angle the turtle is facing
     * @param pen
     *            the pen the turtle is holding
     */

    public Turtle(double x, double y, Pen pen)
    {
        this(x, y, getDefaultAngle(), pen, getDefaultImage());
    }

    /**
     * Creates a turtle with initial position (x,y) facing the given Angle
     * holding the given pen
     * 
     * @param x
     *            the x coordinate
     * @param y
     *            the y coordinate
     * @param angle
     *            the angle the turtle is facing
     * @param pen
     *            the pen the turtle is holding
     */
    public Turtle(double x, double y, Angle angle, Pen pen)
    {
        this(x, y, angle, pen, getDefaultImage());
    }

    public Turtle(double x, double y, Angle angle, Image image)
    {
        this(x, y, angle, getDefaultPen(), image);
    }

    /**
     * Creates a turtle with initial position (x,y) facing the given Angle
     * holding the given pen using the given image
     * 
     * @param x
     *            the x coordinate
     * @param y
     *            the y coordinate
     * @param angle
     *            the angle the turtle is facing
     * @param pen
     *            the pen the turtle is holding
     * @param image
     *            the image of the turtle to be used
     */
    public Turtle(double x, double y, Angle angle, Pen pen, Image image)
    {
        super();

        myId = nextId;
        nextId++;

        isVisible = defaultVisibility;
        myX = x;
        myY = y;
        myAngle = angle;
        myPen = pen;
        myImage = image;
        myName = defaultName + " " + (myId);
    }

    /**
     * Draws the turtle on the canvas. Rotates if necessary.
     * 
     * @param canvasSize
     * @param t
     * @param g2
     */
    public void draw(Graphics2D g2, AbstractCoordinateSystem coordinateSystem)
    {
        if (isVisible)
        {
            Image turtleIcon = rotateTurtleIcon();

            Point location = new Point((int) getX() - myImage.getWidth(null)
                    / 2, (int) getY() + myImage.getHeight(null) / 2);
            location = coordinateSystem.translateCoordinates(location);
            g2.drawImage(turtleIcon, location.x, location.y, null);
        }
    }

    /**
     * Rotates Turtle icon. TODO: Fix cutting off when rotating non-square
     * images.
     * 
     * @returns rotated BufferedImage
     */
    private Image rotateTurtleIcon()
    {
        BufferedImage turtleIcon = (BufferedImage) myImage;
        AffineTransform at = new AffineTransform();
        at.rotate(-getAngle().getPictureRadians(),
                turtleIcon.getWidth(null) / 2, turtleIcon.getHeight(null) / 2);
        AffineTransformOp op = new AffineTransformOp(at,
                AffineTransformOp.TYPE_BILINEAR);
        turtleIcon = op.filter(turtleIcon, null);
        return turtleIcon;
    }

    /**
     * Gets the x-coordinate of the turtle.
     * 
     * @return the x-coordinate
     */
    public double getX()
    {
        return myX;
    }

    /**
     * Get the y-coordinate of the turtle.
     * 
     * @return the y-coordinate
     */
    public double getY()
    {
        return myY;
    }

    /**
     * Moves the turtle to this new position.
     * 
     * @param x
     *            the resulting x coordinate
     * @param y
     *            the resulting y coordinate
     */
    public void move(double x, double y)
    {
        double oldX = myX;
        double oldY = myY;
        myX = x;
        myY = y;
        setChanged();
        notifyObservers(new actions.MoveAction(this, oldX, oldY, myX, myY,
                myAngle, myAngle));
    }

    /**
     * Get the angle to which the turtle is pointing.
     * 
     * @return the angle
     */
    public Angle getAngle()
    {
        return myAngle;
    }

    /**
     * Turns the turtle to face a new angle.
     * 
     * @param radians
     *            the angle in radians to face
     */
    public void turn(double radians)
    {
        Angle oldAngle = myAngle;
        myAngle = new Angle(radians);
        setChanged();
        notifyObservers(new actions.MoveAction(this, myX, myY, myX, myY,
                oldAngle, myAngle));
    }

    /**
     * Updates the current turtle with new information.
     * 
     * @param image
     * @param pen
     * @param visibility
     */
    public void update(Image image, Pen pen, boolean visibility)
    {
        myImage = image;
        myPen = pen;
        isVisible = visibility;
    }

    /**
     * Gets the numeric ID of this turtle.
     * 
     * @return the id
     */
    public int getId()
    {
        return myId;
    }

    /**
     * Gets the turtle's name
     * 
     * @return this turtle's name
     */
    public String getName()
    {
        return myName;
    }

    /**
     * Gets the turtle's pen
     * 
     * @return
     */
    public Pen getPen()
    {
        return myPen;
    }

    /**
     * Puts the pen up.
     */
    public void penUp()
    {
        myPen.penUp();
    }

    /**
     * Puts the pen down.
     */
    public void penDown()
    {
        myPen.penDown();
    }

    /**
     * Get the visibility of the turtle
     * 
     * @return true if the turtle is visible
     */
    public boolean isVisible()
    {
        return isVisible;
    }

    /**
     * Sets the visibility of the turtle
     * 
     * @param visibility
     *            the visibility to set
     */
    public void setVisibility(boolean visibility)
    {
        isVisible = visibility;
        setChanged();
        notifyObservers(new actions.PropertyAction(this, visibility, new Pen(),
                myImage));
    }

    public String toString()
    {
        return getName();
    }

    /**
     * Compares this turtle to another. Each turtle has a unique numeric ID, so
     * we can just compare that.
     * 
     * @param other
     *            the other turtle
     * @return equality
     */
    public boolean equals(Turtle other)
    {
        return myId == other.getId();
    }
}
